package hydrap2p.library.hashers.common;

import hydrap2p.helper.ServiceConnector;
import hydrap2p.helper.ThriftServerThread;
import hydrap2p.helper.ThriftUtils;
import hydrap2p.library.HashGenerator;
import hydrap2p.logging.Level;
import hydrap2p.modulemanager.ModuleControl;
import hydrap2p.servicemanager.NameTaken;

import java.io.IOException;
import java.net.BindException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.TreeMap;
import java.util.Map.Entry;

import org.apache.thrift.TException;
import org.apache.thrift.transport.TTransportException;

/**
 * Starts up several Thrift servers to implement several different hash algorithms
 * @author Hufman
 *
 */
public class HashMain {
	private static Map<String, ThriftServerThread> threads;
	private static ThriftServerThread control;
	private static Timer timer;
	private static MyBoolean running;
	private static final long pingerdelay = 10000;
	private static final long connecttimeout=5000;
	
	public static void main(String[] args)
	{
		String LOGSOURCE="Common Hash Algorithms - Initialization";
		
		threads=new TreeMap<String, ThriftServerThread>();
		
		// try to ping the manager
		try {
			ServiceConnector.getServiceManager().ping();
		} catch (TTransportException e) {
			System.out.println("Failed to connect to the Manager, shutting down Common Hash Generators");
			return;
		} catch (TException e) {
			System.out.println("Weird error while connecting to the Manager, shutting down Common Hash Generators: "+e.getLocalizedMessage());
			return;
		}
		
		// try to ping the library
		long starttime=System.currentTimeMillis();
		boolean found=false;
		while (starttime+connecttimeout>System.currentTimeMillis())
		{
			try {
				ServiceConnector.getLibrary().ping();
				// if successfully pung, skip out of the loop
				found=true;
				break;
			} catch (Exception e) {
				// could not ping, try again
			}
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
			}
		}

		// complain if it didn't work
		if (!found){
			System.out.println("Could not connect to the Library, check that it's running");
			return;
		}
		
		// advertise our controller location
		try {
			control=ThriftUtils.initThriftServer(new ModuleControl.Processor(new HashControl()), "CommonHashersCTL", 2345);
		} catch (BindException e) {
			HashLogging.log(Level.FATAL, LOGSOURCE, "Could not start Module Control interface: "+e.getLocalizedMessage());
		} catch (IOException e) {
			HashLogging.log(Level.FATAL, LOGSOURCE, "Could not start Module Control interface: "+e.getLocalizedMessage());
		} catch (NameTaken e) {
			HashLogging.log(Level.FATAL, LOGSOURCE, "Could not start Module Control interface: "+e.getLocalizedMessage());
		}
		if (control==null)
			return;
		control.start();
		
		// What hash algorithms should we try to load
		Map<String,String> algorithms = new HashMap<String,String>();	// map of Nice Name -> Java MessageDigest Name
		algorithms.put("MD5","MD5");
		algorithms.put("SHA1","SHA-1");
		
		// Create little servers for all of them
		for (Entry<String,String>entry:algorithms.entrySet())
		{
			String algorithm=entry.getKey();
			// Try to load the hash algorithm
			HashServer server;
			try {
				server = new HashServer(algorithm,entry.getValue());
			} catch (NoSuchAlgorithmException e) {
				HashLogging.log(Level.ERROR, LOGSOURCE, "Failed to initialize algorithm "+algorithm);
				continue;
			}
			
			// Register the hash algorithm with the Manager and the Library
			ThriftServerThread thread;
			try {
				thread=ThriftUtils.initThriftServer(new HashGenerator.Processor(server),"HashGenerator."+algorithm, 9234);
			} catch (BindException e) {
				HashLogging.log(Level.ERROR, LOGSOURCE, "Failed to register algorithm "+algorithm+" in HydraP2P: "+e.getLocalizedMessage());
				continue;
			} catch (IOException e) {
				HashLogging.log(Level.ERROR, LOGSOURCE, "Failed to connect to manager");
				continue;
			} catch (NameTaken e) {
				HashLogging.log(Level.ERROR, LOGSOURCE, "This algorithm is already registered");
				continue;
			}
			
			new Thread(new RegisterLibrary(thread, algorithm)).start();
			
			// Start processing on a separate thread
			thread.start();
			
			threads.put(algorithm, thread);
		}
		
		if (!threads.isEmpty())
		{
			running=new MyBoolean(true);
			timer = new Timer();
			timer.schedule(new PingTimer(), pingerdelay, pingerdelay);
			waitUntilShutdown();
		}
		else
		{
			HashLogging.log(Level.FATAL, LOGSOURCE, "No algorithms have started successfully, shutting down");
		}
	}
	
	public static void shutdown()
	{
		timer.cancel();
		for (Entry<String, ThriftServerThread> thread : threads.entrySet())
		{
			// Unregister as a hash function from the library
			try {
				ServiceConnector.getLibrary().unregisterHashGenerator(thread.getKey(), thread.getValue().getLocation());
			} catch (Exception e) {
			}
			// Stop the server thread, which also unregisters from the Manager
			thread.getValue().shutdown();
		}
		control.shutdown();
		synchronized(running)
		{
			running.setState(false);
			running.notify();
		}
		System.exit(0);
	}
	
	private static void waitUntilShutdown()
	{
		while (running.getState())
		{
			synchronized(running)
			{
				if (running.getState())
				{
					try {
						running.wait();
					} catch (InterruptedException e) {
					}
				}
			}
		}
	}

	private static class PingTimer extends TimerTask {

		@Override
		public void run() {
			try {
				ServiceConnector.getServiceManager().ping();
				ServiceConnector.getLibrary().ping();
				return;
			} catch (Exception e) {
			}
			shutdown();
		}
	}
}
